#include "cpu/cpu.h"
#include "cpu/irq.h"
#include "os_cfg.h"
#include "comm/cpu_instr.h"
#include "ipc/mutex.h"

static segment_desc_t GDT[GDT_TABLE_SIZE];
static mutex_t mutex;

void segment_desc_set(int selector, uint32_t base, uint32_t limit, uint16_t attr)
{
    segment_desc_t *p_desc = GDT + (selector >> 3);
    p_desc->limit15_0 = limit & 0xffff;
    p_desc->base15_0 = base & 0xffff;
    p_desc->base23_16 = base >> 16;
    p_desc->attr = attr | (limit >> 16 & 0x0f) << 8;
    p_desc->base31_24 = base >> 24;
}

void init_gdt(void)
{
    for (int i = 0; i < GDT_TABLE_SIZE; i++) {
        segment_desc_set(i << 3, 0, 0, 0);
    }

    segment_desc_set(KERNEL_SELECTOR_DS, 0, 0xffffffff, 
                    SEG_G | SEG_D | SEG_P_PRESENT | SEG_DPL0 | SEG_S_NORMAL | 
                    SEG_TYPE_DATA | SEG_TYPE_RW);

    segment_desc_set(KERNEL_SELECTOR_CS, 0, 0xffffffff, 
                    SEG_G | SEG_D | SEG_P_PRESENT | SEG_DPL0 | SEG_S_NORMAL | 
                    SEG_TYPE_CODE);

    lgdt((uint32_t)GDT, sizeof GDT);
}

void cpu_init(void)
{
    mutex_init(&mutex);
    init_gdt();
}

void gate_desc_set(gate_desc_t *desc, uint16_t selector, uint32_t offset, 
                    uint16_t attr)
{
    desc->offset15_0 = offset & 0xffff;
    desc->selector = selector;
    desc->attr = attr;
    desc->offset31_16 = offset >> 16;
}

int gdt_alloc_desc(void)
{
    mutex_lock(&mutex);

    for (int i = 1; i < GDT_TABLE_SIZE; i++) {
        segment_desc_t *p_desc = GDT + i;
        if (p_desc->attr == 0) {
            mutex_unlock(&mutex);
            return i << 3;
        }
    }
    
    mutex_unlock(&mutex);
    return -1;
}

void switch_to_tss(uint16_t tss_sel)
{
    far_jmp(tss_sel, 0);
}

void gdt_free_sel(int sel)
{
    mutex_lock(&mutex);
    GDT[sel >> 3].attr = 0;
    mutex_unlock(&mutex);
}